﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using GeoFramework;

namespace MapControl
{
    public partial class Map : UserControl, IControlBackground
    {
        #region vars
        private Position center = new Position(new Latitude(0), new Longitude(0));
        private int zoomLevel = 0;
        private string apiKey = string.Empty;
        private MapType mapType = MapType.Roadmap;
        private MapProviders mapProvider = MapProviders.Bing;
        private Rectangle projectionArea = new Rectangle(0, 0, 0, 0);
        
        private IMapProvider mapProviderInstance;
        private Size previousSize;
        private bool isConnected = false;

        private bool isMouseDown = false;
        private int startX = 0;
        private int startY = 0;

        private List<MapObserver> clickObserervers = new List<MapObserver>();
        private List<MapObserver> doubleClickObservers = new List<MapObserver>();
        private bool isInfoOn = false;
        private Bitmap backgroundImage;

        private bool isRefreshing = false;
        #endregion

        #region events
        internal event EventHandler RequiredRecalculatingChanged;
        public event EventHandler PositionChanged;
        public event MapEventHandler MapClicked;
        public event MapEventHandler MapDoubleClicked;
        #endregion

        #region properties
        [EditorBrowsable(EditorBrowsableState.Never)]
        public Position Center
        {
            get
            {
                return center;
            }
            set
            {
                center = value;
                if (IsLoaded)
                {
                    RecalculateProjectionArea();
                    FireRequiredRecalculatingChanged();
                    if (PositionChanged != null)
                        PositionChanged(this, EventArgs.Empty);
                }
            }
        }

        private void RecalculateProjectionArea()
        {
            PointD pd = mapProviderInstance.TilesManager.ProjectionCalculator.FromPosition(center, zoomLevel);
            projectionArea = new Rectangle(Convert.ToInt32(pd.X) - (Width / 2), Convert.ToInt32(pd.Y) - (Height / 2), Width, Height);
            // profiler said that ToString is not quick
            //System.Diagnostics.Debug.WriteLine(string.Format("Recalculating projection area for {0}, zoom {1}: x={2}, y={3}", center, zoomLevel, pd.X, pd.Y));
        }

        public int ZoomLevel {
            get
            {
                return zoomLevel;
            }
            set
            {
                zoomLevel = value;
                if (IsLoaded)
                {
                    navigator1.ZoomLevel = value;
                    FireRequiredRecalculatingChanged();
                }
            }
        }

        public string ApiKey {
            get
            {
                return apiKey;
            }
            set
            {
                apiKey = value;
                mapProviderInstance = ProviderFactory(mapProvider);
                if (IsLoaded)
                    FireRequiredRecalculatingChanged();
            }
        }

        public MapType MapType
        {
            get
            {
                return mapType;
            }
            set
            {
                mapType = value;
                if (IsLoaded)
                    FireRequiredRecalculatingChanged();
            }
        }

        public MapProviders MapProvider 
        {
            get
            {
                return mapProvider;
            }
            set
            {
                mapProvider = value;
                mapProviderInstance = ProviderFactory(mapProvider);
                if (IsLoaded)
                    FireRequiredRecalculatingChanged();
            }
        }

        public bool IsLoaded { get; private set; }
        //public bool ClearCacheOnDispose { get; set; }
        public bool ShowNavigator
        {
            get { return navigator1.Visible; }
            set { navigator1.Visible = value; }
        }

        public Image BackgroundImage
        {
            get
            {
                return backgroundImage;
            }
        }

        public string LoadingText { get; set; }
        public bool ShowTilesBorders { get; set; }

        public bool DesignMode
        {
            get
            {
                return (Site != null && Site.DesignMode);
            }
        }

        internal Rectangle ProjectionArea
        {
            get { return projectionArea; }
        }

        internal IMapProvider MapProviderInstance
        {
            get { return mapProviderInstance; }
        }
        #endregion


        public Map()
        {
            InitializeComponent();
            RequiredRecalculatingChanged += new EventHandler(MapControl_RequiredRecalculatingChanged);
            IsLoaded = !DesignMode;
            previousSize = Size;
            LoadingText = "Loading...";
        }

        public void AddClickObserver(MapObserver observer)
        {
            if(!clickObserervers.Contains(observer))
                clickObserervers.Add(observer);
        }

        public void RemoveClickObserver(MapObserver observer)
        {
            if (clickObserervers.Contains(observer))
                clickObserervers.Remove(observer);
        }

        public void AddDoubleClickObserver(MapObserver observer)
        {
            if(!doubleClickObservers.Contains(observer))
                doubleClickObservers.Add(observer);
        }

        public void RemoveDoubleClickObserver(MapObserver observer)
        {
            if (doubleClickObservers.Contains(observer))
                doubleClickObservers.Remove(observer);
        }

        public void ShowInfo(string msg)
        {
            ShowInfo(msg, 10000);
        }

        public void ShowInfo(string msg, TimeSpan ts)
        {
            ShowInfo(msg, ts.Milliseconds);
        }

        public void ShowInfo(string msg, int ms)
        {
            if (isInfoOn)
            {
                timerInfo.Enabled = false;
            }
            infoBox1.Text = msg;
            timerInfo.Interval = ms;
            timerInfo.Enabled = true;
            isInfoOn = true;
            infoBox1.Visible = true;
        }

        void MapControl_ParentChanged(object sender, EventArgs e)
        {
            isConnected = true;
            FireRequiredRecalculatingChanged();
        }

        #region overrides
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            if (IsLoaded && isConnected && (previousSize.Height != Size.Height || previousSize.Width != Size.Width))
            {
                FireRequiredRecalculatingChanged();
                Refresh();
            }
            previousSize = Size;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            if (DesignMode)
            {
                base.OnPaintBackground(e);
                return;
            }
            foreach (var item in mapProviderInstance.TilesManager.VisibleTiles)
            {
                if (item.TileImage == null)
                {
                    e.Graphics.FillRectangle(new SolidBrush(BackColor), item.VisibleAreaOnMap);
                    e.Graphics.DrawString(LoadingText, new Font("Tahoma", 10, FontStyle.Regular), new SolidBrush(Color.Black), item.VisibleAreaOnMap.X + (item.VisibleAreaOnMap.Width / 2), item.VisibleAreaOnMap.Y + (item.VisibleAreaOnMap.Height / 2));
                }
                else
                    e.Graphics.DrawImage(item.TileImage, item.VisibleAreaOnMap, item.VisibleAreaOfImage, GraphicsUnit.Pixel);
                //bg.DrawImage(item.TileImage, item.VisibleAreaOnMap, item.VisibleAreaOfImage, GraphicsUnit.Pixel);
                if (ShowTilesBorders)
                    e.Graphics.DrawRectangle(new Pen(Color.Red), item.VisibleAreaOnMap);
            }
        }

        public override void Refresh()
        {
            if (isRefreshing)
                return;
            try
            {
                isRefreshing = true;
                base.Refresh();
                if (ShowNavigator)
                    navigator1.Refresh();
            }
            finally
            {
                isRefreshing = false;
            }
        }
 
        #endregion

        void MapControl_RequiredRecalculatingChanged(object sender, EventArgs e)
        {
            if (!IsLoaded || !isConnected)
                return;
            mapProviderInstance.TilesManager.RecalculateVisibleTiles(new Point(0, 0), zoomLevel, MapType, true);
            //backgroundImage = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            RecalculateProjectionArea();
            Refresh();
        }

        private void FireRequiredRecalculatingChanged()
        {
            if (!DesignMode && RequiredRecalculatingChanged != null)
                RequiredRecalculatingChanged(this, EventArgs.Empty);
        }

        private IMapProvider ProviderFactory(MapProviders prov)
        {
            switch (prov)
            {
                case MapProviders.Bing:
                    return new Bing.BingMapProvider(apiKey, this);
                case MapProviders.GMap:
                    throw new NotImplementedException();
                default:
                    throw new InvalidOperationException();
            }
        }

        private void MapControl_MouseDown(object sender, MouseEventArgs e)
        {
            isMouseDown = true;
            startX = e.X;
            startY = e.Y;
        }

        private void MapControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (!isMouseDown)
                return;
            Point p = new Point(e.X - startX, e.Y - startY);
            MoveUsingVector(p);
            startX = e.X;
            startY = e.Y;
        }

        private void MoveUsingVector(Point p)
        {
            PointD cntr = mapProviderInstance.TilesManager.ProjectionCalculator.FromPosition(center, zoomLevel);
            //System.Diagnostics.Debug.WriteLine(string.Format("Previous center={0}, zoom={1}, pxX={2}, pxY={3}", center, zoomLevel, cntr.X, cntr.Y));
            PointD newCntr = cntr.Add(-p.X, -p.Y);
            center = mapProviderInstance.TilesManager.ProjectionCalculator.FromPoint(newCntr, zoomLevel);
            //System.Diagnostics.Debug.WriteLine(string.Format("New center={0}, zoom={1}, pxX={2}, pxY={3}", center, zoomLevel, newCntr.X, newCntr.Y));
            RecalculateProjectionArea();
            mapProviderInstance.TilesManager.RecalculateVisibleTiles(p, zoomLevel, MapType, false);
            if (PositionChanged != null)
                PositionChanged(this, EventArgs.Empty);
            Refresh();
        }

        private void MapControl_MouseUp(object sender, MouseEventArgs e)
        {
            isMouseDown = false;
            Refresh();
        }

        private void navigator1_ZoomChanged(object sender, EventArgs e)
        {
            if (ZoomLevel == navigator1.ZoomLevel)
                return;
            ZoomLevel = navigator1.ZoomLevel;
            Refresh();
        }

        private void MapControl_Click(object sender, EventArgs e)
        {
            if (MapClicked != null)
                MapClicked(this, new MapEventArgs()
                {
                    Point = new Point(startX, startY),
                    Position = mapProviderInstance.TilesManager.ProjectionCalculator
                    .FromPoint(new PointD(projectionArea.X + startX, projectionArea.Y + startY), zoomLevel)
                });
            FireObservers(clickObserervers);
        }

        private void MapControl_DoubleClick(object sender, EventArgs e)
        {
            if (MapDoubleClicked != null)
                MapDoubleClicked(this, new MapEventArgs()
                {
                    Point = new Point(startX, startY),
                    Position = mapProviderInstance.TilesManager.ProjectionCalculator
                    .FromPoint(new PointD(projectionArea.X + startX, projectionArea.Y + startY), zoomLevel)
                });
            FireObservers(doubleClickObservers);
        }

        private void FireObservers(List<MapObserver> observers)
        {
            //System.Diagnostics.Debug.WriteLine(string.Format("Trigger for x={0} ({3}), y={1} ({4}), coords={2}", startX, startY, mapProviderInstance.TilesManager.ProjectionCalculator
            //            .FromPoint(new PointD(projectionArea.X + startX, projectionArea.Y + startY), zoomLevel), projectionArea.X + startX, projectionArea.Y + startY));

            foreach (var item in observers.FindAll(x => startX >= x.Area.X && startX <= x.Area.Right 
                && startY >= x.Area.Y && startY <= x.Area.Bottom))
            {
                if (item.Action != null)
                    item.Action(this, new MapEventArgs() {
                        Point = new Point(startX, startY),
                        Position = mapProviderInstance.TilesManager.ProjectionCalculator
                        .FromPoint(new PointD(projectionArea.X + startX, projectionArea.Y + startY), zoomLevel) });
            }
        }

        private void timerInfo_Tick(object sender, EventArgs e)
        {
            infoBox1.Visible = false;
            isInfoOn = false;
            timerInfo.Enabled = false;
        }
    }
}